package day28.homework;

import java.util.ArrayList;
import java.util.List;

/**
 * 包子铺类，负责管理包子的制作和售卖
 * 实现生产者-消费者模式
 * 
 * HashMap：哈希表实现，无序
 * LinkedHashMap：保持插入顺序
 * TreeMap：按键的自然顺序或自定义顺序排序
 * 
 * 这个类使用了线程同步机制，通过synchronized关键字保证线程安全
 * 使用wait()和notifyAll()方法实现线程间的等待和通知
 * 
 * 多线程相关知识点：
 * 1. synchronized关键字：确保同一时间只有一个线程能执行被修饰的方法或代码块
 * 2. wait()方法：使当前线程进入等待状态，并释放对象锁，其他线程可以获取该对象锁
 * 3. notifyAll()方法：唤醒在该对象上等待的所有线程
 * 4. 线程间通信：通过共享对象（BunShop实例）实现线程间的协调工作
 * 5. 虚假唤醒：使用while循环检查条件而不是if语句，防止线程被意外唤醒后继续执行
 */
public class BunShop {
    /** 
     * 存放肉馅包子的容器
     * 使用ArrayList实现，支持动态扩容
     * 线程安全通过synchronized方法实现 
     */
    private List<Bun> meatBuns = new ArrayList<>();
    
    /** 
     * 存放蔬菜馅包子的容器
     * 使用ArrayList实现，支持动态扩容
     * 线程安全通过synchronized方法实现 
     */
    private List<Bun> vegetableBuns = new ArrayList<>();
    
    /** 
     * 容器最大容量，防止内存溢出
     * 当包子数量达到此值时，生产者线程会等待消费者消费包子
     */
    private static final int MAX_CAPACITY = 10;
    
    /**
     * 制作包子方法，由包子师傅调用
     * 使用synchronized关键字保证同一时间只有一个线程能执行此方法
     * 当容器已满时，线程会调用wait()方法进入等待状态，直到有空间可用
     * 制作完成后调用notifyAll()方法唤醒所有等待的线程
     * 
     * 线程同步机制详解：
     * 1. synchronized关键字：确保同一时间只有一个线程能执行此方法
     *    - 当一个线程进入该方法时，会获取BunShop对象的内部锁（monitor）
     *    - 其他线程尝试进入此方法时会被阻塞，直到锁被释放
     *    - 方法执行完毕或发生异常时，锁会自动释放
     * 
     * 2. wait()方法：当包子容器已满时，生产者线程进入等待状态
     *    - 调用wait()方法会使当前线程释放BunShop对象的锁
     *    - 线程进入该对象的等待队列中
     *    - 直到其他线程调用notify()或notifyAll()方法才会被唤醒
     *    - 被唤醒后需要重新获取锁才能继续执行
     * 
     * 3. notifyAll()方法：通知所有等待的线程
     *    - 当制作完包子后，调用notifyAll()方法唤醒所有在该对象上等待的线程
     *    - 包括等待包子的消费者线程和因容器满而等待的其他生产者线程
     *    - 被唤醒的线程需要重新竞争锁，获得锁的线程才能继续执行
     * 
     * @param flavor 包子口味（肉馅或蔬菜馅）
     * @throws InterruptedException 当线程在等待过程中被中断时抛出此异常
     */
    public synchronized void makeBun(Bun.Flavor flavor) throws InterruptedException {
        // 根据口味选择对应的容器
        // 如果是肉馅包子，选择meatBuns容器；否则选择vegetableBuns容器
        List<Bun> buns = flavor == Bun.Flavor.MEAT ? meatBuns : vegetableBuns;
        
        // 如果容器已满，则等待消费者消费
        // 使用while循环而不是if判断是为了防止虚假唤醒
        // 虚假唤醒是指线程在没有被notify/notifyAll唤醒的情况下自动苏醒
        while (buns.size() >= MAX_CAPACITY) {
            System.out.println("包子铺: " + flavor + "包子已满，等待售卖...");
            // 调用wait()方法使当前线程进入等待状态，并释放对象锁
            // 其他线程可以获取该对象锁并执行同步方法
            // 线程会一直等待直到被notify/notifyAll唤醒或被中断
            wait();
        }
        
        // 制作包子
        // 创建指定口味的新包子对象
        Bun bun = new Bun(flavor);
        // 将包子添加到对应的容器中
        buns.add(bun);
        System.out.println("包子铺: 制作了一个" + bun);
        
        // 通知所有等待的线程，有新的包子制作完成
        // notifyAll()会唤醒所有在该对象上等待的线程
        // 包括等待包子的消费者线程和因容器满而等待的其他生产者线程
        notifyAll();
    }
    
    /**
     * 购买包子方法，由顾客调用
     * 使用synchronized关键字保证同一时间只有一个线程能执行此方法
     * 当没有包子时，线程会调用wait()方法进入等待状态，直到有包子可用
     * 购买完成后调用notifyAll()方法唤醒所有等待的线程
     * 
     * 线程同步机制详解：
     * 1. synchronized关键字：确保同一时间只有一个线程能执行此方法
     *    - 当一个线程进入该方法时，会获取BunShop对象的内部锁（monitor）
     *    - 其他线程尝试进入此方法时会被阻塞，直到锁被释放
     *    - 方法执行完毕或发生异常时，锁会自动释放
     * 
     * 2. wait()方法：当没有包子时，消费者线程进入等待状态
     *    - 调用wait()方法会使当前线程释放BunShop对象的锁
     *    - 线程进入该对象的等待队列中
     *    - 直到其他线程调用notify()或notifyAll()方法才会被唤醒
     *    - 被唤醒后需要重新获取锁才能继续执行
     * 
     * 3. notifyAll()方法：通知所有等待的线程
     *    - 当购买完包子后，调用notifyAll()方法唤醒所有在该对象上等待的线程
     *    - 包括等待制作包子的生产者线程和因容器满而等待的其他生产者线程
     *    - 被唤醒的线程需要重新竞争锁，获得锁的线程才能继续执行
     * 
     * @param flavor 包子口味（肉馅或蔬菜馅）
     * @return 包子对象
     * @throws InterruptedException 当线程在等待过程中被中断时抛出此异常
     */
    public synchronized Bun buyBun(Bun.Flavor flavor) throws InterruptedException {
        // 根据口味选择对应的容器
        // 如果是肉馅包子，选择meatBuns容器；否则选择vegetableBuns容器
        List<Bun> buns = flavor == Bun.Flavor.MEAT ? meatBuns : vegetableBuns;
        
        // 如果没有该口味的包子，则等待生产者制作
        // 使用while循环而不是if判断是为了防止虚假唤醒
        // 虚假唤醒是指线程在没有被notify/notifyAll唤醒的情况下自动苏醒
        while (buns.isEmpty()) {
            System.out.println("顾客: " + Thread.currentThread().getName() + "想要买" + flavor + "包子，但已售完，等待制作...");
            // 调用wait()方法使当前线程进入等待状态，并释放对象锁
            // 其他线程可以获取该对象锁并执行同步方法
            // 线程会一直等待直到被notify/notifyAll唤醒或被中断
            wait();
        }
        
        // 购买包子（从容器中移除一个包子）
        // remove(0)移除并返回列表中的第一个元素
        Bun bun = buns.remove(0);
        System.out.println("顾客: " + Thread.currentThread().getName() + "买到了一个" + bun);
        
        // 通知所有等待的线程，有包子被购买了
        // notifyAll()会唤醒所有在该对象上等待的线程
        // 包括等待制作包子的生产者线程和因容器满而等待的其他生产者线程
        notifyAll();
        
        return bun;
    }
}